﻿using Jacobi.Vst.Core;
using Jacobi.Vst.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Aleator.Core;
using Aleator.Helpers;

namespace Aleator
{
#pragma warning disable 1591

	/// <summary>
	/// Implements the incoming Midi event handling for the plugin.
	/// </summary>
	public class MidiProcessor : IVstMidiProcessor
	{
		private Plugin _plugin;
		private DateTime _empty = DateTime.MinValue;
		private bool _dead;

		//May need this for VST compliance
		//private double m_tempo;

		/// <summary>
		/// Current tempo of the host
		/// </summary>
		public double Tempo
		{
			get
			{
				//TODO: Notifying UI of tempo changes from host. 1 is the minimum allowed so just return that if there's no sequencer
				//detected.
				if ((_plugin.PluginAudioProcessor as AudioProcessor) == null
				    || (_plugin.PluginAudioProcessor as AudioProcessor).TimeInfo == null)
					return 120.00;
				else
					return (_plugin.PluginAudioProcessor as AudioProcessor).TimeInfo.Tempo;
			}
		}

		/// <summary>
		/// Constructs a new instance.
		/// </summary>
		/// <param name="plugin">Must not be null.</param>
		public MidiProcessor(Plugin plugin)
		{
			_plugin = plugin;
			Events = new VstEventCollection();
			NoteOnEvents = new Queue<byte>();
		}

		/// <summary>
		/// Gets the midi events that should be processed in the current cycle.
		/// </summary>
		public VstEventCollection Events { get; private set; }

		/// <summary>
		/// The raw note on note numbers.
		/// </summary>
		public Queue<byte> NoteOnEvents { get; private set; }

		#region IVstMidiProcessor Members

		public int ChannelCount
		{
			get { return _plugin.ChannelCount; }
		}

		public void Process(VstEventCollection events)
		{
			foreach (VstEvent evnt in events)
			{
				if (evnt.EventType != VstEventTypes.MidiEvent) continue;

				VstMidiEvent midiEvent = (VstMidiEvent)evnt;
				VstMidiEvent mappedEvent = null;

				if (((midiEvent.Data[0] & 0xF0) == 0x80 || (midiEvent.Data[0] & 0xF0) == 0x90) || (midiEvent.Data[0] & 0xF0) == 0xb0)
				{
					byte[] midiData = new byte[4];
					midiData[0] = midiEvent.Data[0];
					midiData[1] = midiEvent.Data[1];
					midiData[2] = midiEvent.Data[2];

					Note.Description description = (Note.Description)((int)midiData[0]);

					Debug.WriteLine(string.Format("Processing Event: Data[0] | {0}, Data[1] | {1}, Data[2] | {2}, Description | {3}", midiData[0], midiData[1], midiData[2], description.ToString()));

					mappedEvent = new VstMidiEvent(midiEvent.DeltaFrames,
					    midiEvent.NoteLength,
					    midiEvent.NoteOffset,
					    midiData,
					    midiEvent.Detune,
					    midiEvent.NoteOffVelocity);

					Events.Add(mappedEvent);

					// add raw note-on note numbers to the queue
					if ((midiEvent.Data[0] & 0xF0) == 0x90)
					{
						lock (((ICollection)NoteOnEvents).SyncRoot)
						{
							NoteOnEvents.Enqueue(midiEvent.Data[1]);
						}
					}
				}
			}

			if (Events.Count > 0)
			{
				try
				{
					_plugin.PluginAudioProcessor.Process(null, null);
				}
				catch (Exception ex)
				{
					//Do nothing
				}
			}
			else if (_empty == DateTime.MinValue && !_dead)
			{
				_empty = DateTime.Now;
			}
			else if (((TimeSpan)(DateTime.Now - _empty)).Minutes > 5 && !_dead)
			{
				Logger.Log(TraceEventType.Error, "The Aleator process has run for 5 minutes without generating events, please restart.");
				_dead = true;
			}

		}

		#endregion
	}

#pragma warning restore 1591
}
